<script lang="ts">
  import { onDestroy, onMount, createEventDispatcher, tick } from 'svelte';
  const emit = createEventDispatcher();

  import Vtuber from './Vtuber.svelte';
  import HeaderControl from './HeaderControl.svelte';
  import FooterControl from './FooterControl.svelte';
  import Caption from './Caption.svelte';
  import InputSearch from './InputSearch.svelte';
  import Loading from './Loading.svelte';
  import Message from './Message.svelte';

  import Broadcast from '../utils/broadcast';
  import useDrag from '../hooks/useDrag';
  import config from '../config/index';
  import {
    type BroadcastData,
    type CaptionData,
    BroadcastType,
    VolumeStatus,
    BroadcastStatus,
    BroadcastContentType,
    type Position
  } from '../types/vtuber';
  import { errorReport, sendStat, setPosition, debounce } from '../utils/tools';

  export let isShow: boolean = true;

  export let position: Position;

  const { baseUrl, userId, wsBaseUrl } = config;
  // 是否在播报
  let isPlaying: boolean = false;
  // 当前播报的顺序
  let nowIndex: number = 0;
  // 播报内容
  let value: string = '';
  // 声音状态
  let volumeStatus: VolumeStatus = VolumeStatus.Normal;
  // 是否展示自动播报文案
  let autoplaytipsIsShow: number = 0;
  // 虚拟人实例
  let vtuber: Vtuber | void;
  let originCaptionData: CaptionData = {
    nowValue: '',
    removedValue: '',
    remainValue: ''
  };
  const isVtuber = (vtuberParam: Vtuber | void): vtuberParam is Vtuber => !!vtuberParam;
  // 结束播报
  const stop = () => {
    if (!isVtuber(vtuber)) {
      return;
    }
    isPlaying = false;
    vtuber.stop();
  };
  // 静音和开启音量
  const switchVolume = () => {
    if (!isVtuber(vtuber)) {
      return;
    }
    volumeStatus = volumeStatus === VolumeStatus.Mute ? VolumeStatus.Normal : VolumeStatus.Mute;
    vtuber.setVolume(volumeStatus);
  };
  // 引擎初始化结果
  let initResult: boolean = false;
  const updateInitResult = ({ detail: { initResult: initResultParam } }) => {
    initResult = initResultParam;
    if (initResult) {
      initBroadcast();
    }
  };
  // 当前播报完成
  const updateFinish = ({ detail: { isFinish } }) => {
    if (!isFinish) {
      return;
    }
    stop();
    // 播放完成需要将当前播报的最后一句塞到remove队列中
    const { nowValue, removedValue, remainValue } =
      nowBroadcastData.broadcastContents[nowIndex].content;
    nowBroadcastData.broadcastContents[nowIndex].content = {
      remainValue,
      removedValue: removedValue + nowValue,
      nowValue: ''
    };
    broadcast.playChangeCaption({
      remainValue,
      removedValue: removedValue + nowValue,
      nowValue: ''
    });
    if (!isPartOver()) {
      broadcast.next();
    } else if (nowBroadcastType !== BroadcastType.ExaminingStock) {
      // 只有非诊股才需要自动获取数据播报
      broadcast.nextPart({ broadcastType: BroadcastType.LiveInformation });
      turnonPollgetData();
    }
  };
  // 获取字幕信息
  const updateCaptionData = ({ detail: { captionData } }) => {
    // 正在播放时关闭轮询
    turnoffPollgetData();
    isPlaying = true;
    emit('updateIsPlaying', { isPlayingStatus: true });
    nowBroadcastType = nowBroadcastData.broadcastType;
    broadcast.playChangeCaption(captionData);
    nowBroadcastData.broadcastContents[nowIndex].content = captionData;
  };

  // 最小化
  const setMin = (isClose: boolean = false) => {
    emit('toggleMiniStatus', { isClose, isMiniStatus: true });
  };

  // 防止延时关闭期间重新打开，设置定时器
  let quitWsEngineTimer;
  // 结束播放
  const endBroadcast = (needTag: boolean = true) => {
    stop();
    broadcast && broadcast.endBroadcast(needTag);
    nowBroadcastType = 'stop';
    turnoffPollgetData();
    if (!needTag) {
      setMin(true);
      // 动画200ms,稍微晚点再断开长连接和退出引擎
      quitWsEngineTimer = setTimeout(() => {
        vtuber && vtuber.quitWsEngine();
      }, 500);
    }
  };
  let broadcastDiv: HTMLElement | void;

  const { setDragAble, removeDragAble } = useDrag();
  let nowBroadcastData: BroadcastData | undefined;
  let nowBroadcastType: BroadcastType | 'stop';

  let isAutoPlay: boolean = false;
  // 需要来自class的监听
  let hasContinueBroadcast: boolean = false;
  let broadcastStatus: BroadcastStatus | void;

  // 是否是切换播报part，通过数字的递增来不断触发动画
  let isPartChangeTips: number = 0;

  const isPartOver = () => {
    const { index, broadcastContents } = nowBroadcastData;
    return broadcastContents.length - 1 <= index;
  };
  // 监听当前播报内容
  const listenNowBroadcast = (broadcastData: BroadcastData, isNext: boolean) => {
    if (!isNext) {
      isPartChangeTips++;
      autoplaytipsIsShow = 0;
    }

    // 此处将后续赋值nowBroadcastData的任务加入到宏任务队列，防止因为动画渲染问题造成nextpart数据覆盖问题
    tick().then(() => {
      nowBroadcastData = broadcastData;
      nowIndex = nowBroadcastData.index;
      const { index, broadcastContents } = broadcastData;
      const contents = broadcastContents[index];
      if (!contents) {
        return;
      }
      const { contentType, content } = contents;
      // 只有虚拟人类型才需要播报
      stop();
      if ([BroadcastContentType.Vtuber, BroadcastContentType.Greeting].includes(contentType)) {
        value = content.remainValue;
      } else if (!isPartOver()) {
        broadcast.next();
      }
      nowBroadcastType = broadcastData.broadcastType;
      originCaptionData.removedValue =
        nowBroadcastData.broadcastContents[nowIndex].content.removedValue;
    });
  };

  // 监听是否有上一个实时解盘
  const listenHasLastLiveBroadcast = (hasLastLiveBroadcast: boolean) => {
    hasContinueBroadcast = hasLastLiveBroadcast;
  };

  // 监听当前的播报状态
  const listenStatus = (status: BroadcastStatus) => {
    broadcastStatus = status;
    emit('updateBroadcastStatus', { broadcastStatus });
  };
  let broadcast: Broadcast;
  const initBroadcast = () => {
    // 用class将字幕逻辑和播放内容逻辑单独出去，不用被svelte编译成响应式数据，也将逻辑抽离更清晰
    broadcast = new Broadcast({
      listenNowBroadcast,
      listenStatus,
      listenHasLastLiveBroadcast
    });
    broadcast.getAutoPlayStatus().then((autoplayStatus) => {
      isAutoPlay = !!autoplayStatus;
    });
  };
  // 诊股
  const stockDiagnosis = ({ detail: { stockCode, stockName } }) => {
    broadcast.nextPart({
      broadcastType: BroadcastType.ExaminingStock,
      stock_code: stockCode,
      stock_name: stockName
    });
  };
  // 播放实时解盘
  const playLive = () => {
    sendStat('_dhym_ssjp');
    broadcast.nextPart({ broadcastType: BroadcastType.LiveInformation, needJudgeFirstClick: true });
  };
  // 切换自动播放
  const toggleAutoPlay = () => {
    broadcast.toggleAutoPlay();
    isAutoPlay = !isAutoPlay;
    const stat = isAutoPlay ? '_dhym_kqzdbb_1' : '_dhym_gbzdbb_1';
    sendStat(stat);
    if (!isAutoPlay) {
      turnoffPollgetData();
    }
    autoplaytipsIsShow++;
  };
  // 点击字幕上的实时解盘按钮
  const broadcastLive = () => {
    broadcast.firstEnterClickExamine();
    broadcast.toggleAutoPlay();
    isAutoPlay = !isAutoPlay;
  };

  // 当前页面是否可见
  let isWindowVisible: boolean = true;
  // 初始化onshow协议
  const initOnShow = () => {
    try {
      const e = window.API.createSessionId('external');
      window.API.use({
        method: 'external.registerEvent',
        data: 'onshow',
        persistent: true,
        sessionId: e,
        callbackName: 'onshow',
        success: (data: number) => {
          isWindowVisible = !!data;
          if (!isWindowVisible) {
            endBroadcast(false);
          }
        }
      });
    } catch (error) {
      errorReport({
        name: `客户端显隐方法失败 ${error.name}`,
        message: error.message,
        stack: error.stack
      });
    }
  };
  initOnShow();
  let timer = null;
  // 轮询获取实时解盘数据,每分钟一次
  const turnonPollgetData = () => {
    timer = setInterval(() => {
      if (broadcast && isWindowVisible) {
        broadcast.nextPart({ broadcastType: BroadcastType.LiveInformation });
      }
    }, 1000 * 30);
  };
  const turnoffPollgetData = () => {
    timer && clearInterval(timer);
  };
  const clickVtuber = debounce(() => {
    // 当前处于停止状态，并且不是首次进入，并且都是问候语，点击ai助手才播报
    if (
      [BroadcastType.ExaminingStock, 'stop'].includes(nowBroadcastType) &&
      broadcastStatus !== void 0 &&
      (nowBroadcastData.broadcastContents.every(
        (item) => item.contentType === BroadcastContentType.Greeting
      ) ||
        !nowBroadcastData.broadcastContents.length)
    ) {
      broadcast.randomBroadcast();
    }
  }, 200);

  let openAnimationSign: boolean = false;
  // 动画完成回调
  const onAnimationEnd = (event) => {
    const animationName = event.animationName;
    // 只对展开收起动画进行处理
    if (animationName.includes('expandAnimation')) {
      openAnimationSign = true;
    } else if (animationName.includes('collapseAnimation')) {
      openAnimationSign = false;
    }
  };

  let hoverd: boolean = true;
  // 是否聚焦输入框
  let isFocus: boolean = false;
  let setTimeoutTimer;
  const mouseenter = () => {
    hoverd = true;
    setTimeoutTimer && clearTimeout(setTimeoutTimer);
  };
  const mouseleave = () => {
    setTimeoutTimer = setTimeout(() => {
      hoverd = false;
    }, 200);
  };
  const setIsFocus = ({ detail: { isFocus: focusStatus } }) => {
    isFocus = focusStatus;
  };

  let isPlayingStatus: boolean = false;
  $: {
    isPlayingStatus = isPlaying || nowBroadcastType === BroadcastType.LiveInformation;
    emit('updateIsPlaying', { isPlayingStatus });
  }

  // 退出状态下放大，需要播报欢迎语
  const setMaxPlay = () => {
    if (
      !isPlayingStatus &&
      nowBroadcastType !== BroadcastType.ExaminingStock &&
      initResult &&
      !broadcast.getHasNowBroadcast()
    ) {
      broadcast.welcomeBroadcast();
    }
  };

  // 是否应该使用兼容性模式
  let needTolerent: boolean = false;
  // 提示语
  let text: string = '';
  // 展示提示语
  let showMessage: boolean = false;
  const updateNeedTolerent = ({ detail: { needTolerent: status, textStr, isLag } }) => {
    needTolerent = status;
    text = textStr;

    if (needTolerent && textStr) {
      isLag && endBroadcast();
      showMessage = true;
      setTimeout(() => {
        showMessage = false;
      }, 3000);
    }
  };

  onMount(() => {
    quitWsEngineTimer && clearTimeout(quitWsEngineTimer);
    if (broadcastDiv) {
      setPosition(position, broadcastDiv);
      setDragAble(broadcastDiv);
    }
  });
  onDestroy(() => {
    if (broadcastDiv) {
      removeDragAble(broadcastDiv);
    }
    vtuber && vtuber.quitWsEngine();
    turnoffPollgetData();
  });
  export { endBroadcast, setMaxPlay };
</script>

<div
  bind:this={broadcastDiv}
  class="broadcast {isShow ? 'show' : 'hidden'} hoverd"
  on:animationend={onAnimationEnd}
  on:mouseenter={mouseenter}
  on:mouseleave={mouseleave}
>
  {#if showMessage}
    <Message {text} />
  {/if}
  <div class="animation-container {openAnimationSign ? 'opacity' : ''}">
    <HeaderControl
      {volumeStatus}
      openAnimationSign={openAnimationSign && (hoverd || isFocus)}
      on:toggleVolume={switchVolume}
      on:setEnd={() => {
        endBroadcast(false);
      }}
      on:setMin={setMin.bind(this, false)}
    />
    <Loading isShow={!initResult} />
    <div class="vtuber-container">
      <div
        class="left-container"
        on:animationend={(event) => {
          event.stopPropagation();
        }}
      >
        <div class="opacity-animation {initResult ? 'show' : 'hidden'}">
          <Caption
            {isPartChangeTips}
            {nowBroadcastData}
            {openAnimationSign}
            on:broadcastLive={broadcastLive}
            {isShow}
            {isAutoPlay}
            {autoplaytipsIsShow}
          />
        </div>
        <div
          class="left-container-animation {openAnimationSign && (hoverd || isFocus)
            ? 'move-animation'
            : ''} opacity-animation {initResult ? 'show' : 'hidden'}"
        >
          <FooterControl
            {nowBroadcastType}
            {isAutoPlay}
            {hasContinueBroadcast}
            {broadcastStatus}
            on:stop={endBroadcast.bind(this, true)}
            on:toggleAutoPlay={toggleAutoPlay}
            on:playLive={playLive}
          />
          <InputSearch on:stockDiagnosis={stockDiagnosis} on:setIsFocus={setIsFocus} />
        </div>
      </div>
      <div class="opacity-animation {initResult ? 'show' : 'hidden'}">
        <Vtuber
          bind:this={vtuber}
          {value}
          {originCaptionData}
          {isPlaying}
          vtuberIndex={0}
          sdkInitData={{
            userId,
            baseUrl,
            wsBaseUrl,
            appSecret: '73c4436e98924a6f80065188ccd5ceb7',
            appId: '596f5c853ebe469682366c1692d1481b',
            speed: 100
          }}
          humanInfo={{
            canRotate: false,
            canScale: false,
            distance: 5.3
          }}
          caption={{
            show: false,
            autoPosition: false
          }}
          on:updateInitResult={updateInitResult}
          on:updateFinish={updateFinish}
          on:updateCaptionData={updateCaptionData}
          on:clickVtuber={clickVtuber}
          on:updateNeedTolerent={updateNeedTolerent}
        />
      </div>
    </div>
  </div>
</div>

<style lang="less">
  .bg-mixins(@size: contain, @position: center, @repeat: no-repeat) {
    background-size: @size;
    background-position: @position;
    background-repeat: @repeat;
  }
  @keyframes expandAnimation {
    0% {
      height: 0;
      transform: translateY(450px);
    }
    100% {
      height: 450px;
      transform: translateY(0);
    }
  }
  @keyframes collapseAnimation {
    0% {
      display: block;
      height: 450px;
      transform: translateY(0); /* 初始状态在顶部，向上偏移0 */
    }
    100% {
      height: 0;
      transform: translateY(450px); /* 最后状态在底部，向上偏移100% */
      display: none;
    }
  }

  .broadcast {
    display: none;
    width: 460px;
    height: 0;
    position: absolute;
    border-radius: 2px;
    overflow: hidden;
    mix-blend-mode: screen;
    &.hoverd {
      background-color: rgba(1, 18, 60, 0.2);
      background-image: url('//i.thsi.cn/staticS3/mobileweb-upload-static-server.img/m/atom/42277020-f86b-4d7f-b893-680b40258661.png');
      background-size: 423px 390px;
      backdrop-filter: blur(20px);
      :global(.vtuber-message) {
        background-color: rgba(0, 92, 255, 0.3) !important;
      }
      :global(.user-message) {
        background-color: rgba(190, 190, 190, 0.2) !important;
      }
      &::before {
        pointer-events: none;
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        border-top: 1px solid;
        border-left: 1px solid;
        border-image-slice: 1;
        border-image-source: linear-gradient(
          to right,
          rgba(255, 255, 255, 0.25),
          rgba(255, 255, 255, 0.6),
          rgba(255, 255, 255, 0.15),
          rgba(255, 255, 255, 0.15),
          rgba(255, 255, 255, 0.15)
        );
        border-radius: 2px;
      }
      &::after {
        pointer-events: none;
        content: '';
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        border-bottom: 1px solid;
        border-right: 1px solid;
        border-image-slice: 1;
        border-image-source: linear-gradient(
          to left,
          rgba(255, 255, 255, 0.25),
          rgba(255, 255, 255, 0.6),
          rgba(255, 255, 255, 0.15),
          rgba(255, 255, 255, 0.15),
          rgba(255, 255, 255, 0.15)
        );
        border-radius: 2px;
      }
    }
    &.show {
      display: block;
      animation: expandAnimation 0.2s;
      animation-fill-mode: forwards;
    }
    &.hidden {
      animation: collapseAnimation 0.2s;
      animation-fill-mode: forwards;
    }

    .bg-mixins(contain,center,no-repeat);
    cursor: move;
    z-index: 999999;
    .left-container {
      position: relative;
      margin-left: 16px;
      margin-right: 30px;
      height: 400px;
      &-animation {
        position: absolute;
        bottom: 8px;
        opacity: 0 !important;
        transform: translateY(20px);
        transition:
          transform 0.4s,
          opacity 0.4s;
        &.move-animation {
          opacity: 1 !important;
          transform: translateY(0);
        }
      }
    }
  }
  .opacity-animation {
    &.show {
      opacity: 1;
      transition: opacity 200ms ease-in-out;
    }
    &.hidden {
      opacity: 0;
    }
  }
  .vtuber-container {
    width: 100%;
    display: flex;
  }
  .animation-container {
    opacity: 0;
    transition: opacity 0.4s;
    &.opacity {
      opacity: 1;
    }
  }
</style>
